﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using ConfigGen.Core.ConfigurationSettings;
using ConfigGen.Core.TemplateLoader;
using Microsoft.Practices.Unity;
using NUnit.Framework;

namespace ConfigGen.Core.Tests.EndToEndTests
{
    [TestFixture]
    public class SingleFileGenerationEndToEndTests
    {
        [Test]
        public void EndToEnd001_SimpleTest()
        {
            string template =
@"<root>
    <child value=""[%ChildValue%]"">
        <grandchild>[%GrandchildValue%]</grandchild>
    </child>
</root>";

            var settings = new List<ConfigurationSetting>
                               {
                                   new ConfigurationSetting("ChildValue", "Child1"),
                                   new ConfigurationSetting("GrandchildValue", "Grandchild1"),
                               };
            var machineSettings = new MachineConfigurationSettings("machineName", "configFilePath", settings);

            var resultingOutputDom = new XmlDocument();

            var results = GenerateFile(template, machineSettings, resultingOutputDom);
            Assert.IsNotNull(results, "No file generated");
            Assert.IsNotNull(resultingOutputDom.DocumentElement, "result should not be an empty document");

            var child = (XmlAttribute)resultingOutputDom.SelectSingleNode("/root/child/@value");
            Assert.IsNotNull(child, "child attribute should not be null");
            Assert.AreEqual("Child1", child.Value, "Incorrect value for child attribute");
            Assert.IsTrue(results.TokenUseCounts.ContainsKey("ChildValue"), "ChildValue expected in TokenUseCounts");
            Assert.AreEqual(1, results.TokenUseCounts["ChildValue"], "Incorrect count for ChildValue in TokenUseCounts");

            var grandChild = (XmlElement)resultingOutputDom.SelectSingleNode("/root/child/grandchild");
            Assert.IsNotNull(grandChild, "grandChild attribute should not be null");
            Assert.AreEqual("Grandchild1", grandChild.InnerText, "Incorrect value for grandChild element");
            Assert.IsTrue(results.TokenUseCounts.ContainsKey("GrandchildValue"), "GrandchildValue expected in TokenUseCounts");
            Assert.AreEqual(1, results.TokenUseCounts["GrandchildValue"], "Incorrect count for GrandchildValue in TokenUseCounts");

            Assert.AreEqual(0, results.UnrecognisedTokenCounts.Count, "No unrecognised tokens expected");
        }

        [Test]
        public void EndToEnd002_ApplyWhenAttribute()
        {
            string template =
@"<root xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
    <child>
        <grandchild1 cg:applyWhen=""$grandchild=1""/>
        <grandchild2 cg:applyWhen=""$grandchild=2""/>
    </child>
</root>";

            var settings = new List<ConfigurationSetting>
                               {
                                   new ConfigurationSetting("grandchild", "2"),
                               };

            var machineSettings = new MachineConfigurationSettings("machineName", "configFilePath", settings);
            var machinesConfigurationCollection = new MachinesConfigurationCollection(new[] { machineSettings });
      
            var resultingOutputDom = new XmlDocument();

            var results = GenerateFile(template, machinesConfigurationCollection["machineName"], resultingOutputDom);
            Assert.IsNotNull(results, "No file generated");
            Assert.IsNotNull(resultingOutputDom.DocumentElement, "result should not be an empty document");

            var grandchild1 = resultingOutputDom.SelectSingleNode("/root/child/grandchild1");
            Assert.IsNull(grandchild1, "grandchild1 should not be present in output");

            var grandchild2 = resultingOutputDom.SelectSingleNode("/root/child/grandchild2");
            Assert.IsNotNull(grandchild2, "grandchild2 should be present in output (case insensitivity check)");

            Assert.IsTrue(results.TokenUseCounts.ContainsKey("grandchild"), "grandchild expected in TokenUseCounts");
            Assert.AreEqual(2, results.TokenUseCounts["grandchild"], "Incorrect count for Grandchild in TokenUseCounts");
        }

        [Test]
        public void EndToEnd003_UnusedAndUnrecognisedTokens()
        {
            string template =
@"<root xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
    <child>
        <grandchild1>[%Grandchild1%]</grandchild1>
        <grandchild3>[%Grandchild3%]</grandchild3>
    </child>
</root>";

            var settings = new List<ConfigurationSetting>
                               {
                                   new ConfigurationSetting("Grandchild1", "1"),
                                   new ConfigurationSetting("Grandchild2", "3"),
                               };
            var machineSettings = new MachineConfigurationSettings("machineName", "configFilePath", settings);

            var resultingOutputDom = new XmlDocument();

            var results = GenerateFile(template, machineSettings, resultingOutputDom);
            Assert.IsNotNull(results, "No file generated");

            Assert.IsNotNull(resultingOutputDom.DocumentElement, "result should not be an empty document");

            var grandchild3 = resultingOutputDom.SelectSingleNode("/root/child/grandchild3");
            Assert.IsNotNull(grandchild3, "grandchild3 should be present in output");
            Assert.AreEqual(string.Empty, grandchild3.InnerText, "Unrecognised token in grandChild3 should have been replaced with empty string");

            Assert.AreEqual(1, results.UnrecognisedTokenCounts.Count, "Incorrect number of unrecognised tokens");
            Assert.AreEqual("Grandchild3", results.UnrecognisedTokenCounts.Keys.FirstOrDefault(), "Incorrect unrecogised token");

            Assert.AreEqual(2, results.TokenUseCounts.Count, "Incorrect count for TokenUseCounts");
            Assert.IsTrue(results.TokenUseCounts.ContainsKey("Grandchild1"), "Grandchild1 not found in TokenUsedCounts");
            Assert.AreEqual(1, results.TokenUseCounts["Grandchild1"], "Incorrect TokenUsedCounts count for Grandchild1");
        }

        [Test]
        public void EndToEnd003_UsedTokenIsCorrectlyCounted()
        {
            string template =
@"<root xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
    <child>
        <grandchild1>[%Grandchild1%]</grandchild1>
    </child>
</root>";

            var settings = new List<ConfigurationSetting>
                               {
                                   new ConfigurationSetting("Grandchild1", "1")
                               };
            var machineSettings = new MachineConfigurationSettings("machineName", "configFilePath", settings);

            var resultingOutputDom = new XmlDocument();

            var results = GenerateFile(template, machineSettings, resultingOutputDom);
            Assert.IsNotNull(results, "No file generated");

            Assert.AreEqual(1, results.TokenUseCounts.Count, "Incorrect count for TokenUseCounts");
            Assert.IsTrue(results.TokenUseCounts.ContainsKey("Grandchild1"), "Grandchild1 not found in TokenUsedCounts");
            Assert.AreEqual(1, results.TokenUseCounts["Grandchild1"], "Incorrect TokenUsedCounts count for Grandchild1");
        }

        [Test]
        public void EndToEnd004_UnusedTokenIsCorrectlyIdentified()
        {
            string template =
@"<root xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
    <child>
        <grandchild1>[%Grandchild1%]</grandchild1>
    </child>
</root>";

            var settings = new List<ConfigurationSetting>
                               {
                                   new ConfigurationSetting("Grandchild1", "1"),
                                   new ConfigurationSetting("Grandchild2", "2")
                               };
            var machineSettings = new MachineConfigurationSettings("machineName", "configFilePath", settings);

            var resultingOutputDom = new XmlDocument();

            var results = GenerateFile(template, machineSettings, resultingOutputDom);
            Assert.IsNotNull(results, "No file generated");

            Assert.IsTrue(results.TokenUseCounts.ContainsKey("Grandchild2"), "Grandchild2 not found in TokenUsedCounts");
            Assert.AreEqual(0, results.TokenUseCounts["Grandchild2"], "Incorrect TokenUsedCounts count for Grandchild2");
            Assert.AreEqual(2, results.TokenUseCounts.Count, "Incorrect count for TokenUseCounts");
        }

        [Test]
        public void EndToEnd005_UnrecognisedTokenInTemplateIsCorrectlyIdentified()
        {
            string template =
@"<root xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
    <child>
        <grandchild1>[%Grandchild1%]</grandchild1>
    </child>
</root>";

            var settings = new List<ConfigurationSetting>();

            var machineSettings = new MachineConfigurationSettings("machineName", "configFilePath", settings);

            var resultingOutputDom = new XmlDocument();

            var results = GenerateFile(template, machineSettings, resultingOutputDom);
            Assert.IsNotNull(results, "No file generated");

            Assert.AreEqual(1, results.UnrecognisedTokenCounts.Count, "Incorrect count for UnrecognisedTokenCounts");
            Assert.IsTrue(results.UnrecognisedTokenCounts.ContainsKey("Grandchild1"), "Grandchild1 not found in UnrecognisedTokenCounts");
            Assert.AreEqual(1, results.UnrecognisedTokenCounts["Grandchild1"], "Incorrect UnrecognisedTokenCounts count for Grandchild1");
        }

        [Test]
        public void EndToEnd006_TemplatePreferenceSpecifyDifferentDelimiters()
        {
            string template =
@"<root xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
    <cg:TemplatePreferences>
        <cg:Token startDelimiter=""{%"" endDelimiter=""%}"" />
    </cg:TemplatePreferences>
    <child value=""{%ChildValue%}"">
        <grandchild>{%GrandchildValue%}</grandchild>
    </child>
</root>";

            var settings = new List<ConfigurationSetting>
                               {
                                   new ConfigurationSetting("ChildValue", "Child1"),
                                   new ConfigurationSetting("GrandchildValue", "Grandchild1"),
                               };
            var machineSettings = new MachineConfigurationSettings("machineName", "configFilePath", settings);

            var resultingOutputDom = new XmlDocument();

            var results = GenerateFile(template, machineSettings, resultingOutputDom);
            Assert.IsNotNull(results, "No file generated");

            Assert.IsNotNull(resultingOutputDom.DocumentElement, "result should not be an empty document");

            var child = (XmlAttribute)resultingOutputDom.SelectSingleNode("/root/child/@value");
            Assert.IsNotNull(child, "child attribute should not be null");
            Assert.AreEqual("Child1", child.Value, "Incorrect value for child attribute");
            Assert.IsTrue(results.TokenUseCounts.ContainsKey("ChildValue"), "ChildValue expected in TokenUseCounts");
            Assert.AreEqual(1, results.TokenUseCounts["ChildValue"], "Incorrect count for ChildValue in TokenUseCounts");

            var grandChild = (XmlElement)resultingOutputDom.SelectSingleNode("/root/child/grandchild");
            Assert.IsNotNull(grandChild, "grandChild attribute should not be null");
            Assert.AreEqual("Grandchild1", grandChild.InnerText, "Incorrect value for grandChild element");
            Assert.IsTrue(results.TokenUseCounts.ContainsKey("GrandchildValue"), "GrandchildValue expected in TokenUseCounts");
            Assert.AreEqual(1, results.TokenUseCounts["GrandchildValue"], "Incorrect count for GrandchildValue in TokenUseCounts");

            Assert.AreEqual(0, results.UnrecognisedTokenCounts.Count, "No unrecognised tokens expected");
        }

        private SingleFileGenerationResults GenerateFile(string template, MachineConfigurationSettings configurationSettings, XmlDocument resultingOutputDom)
        {
            var preferences = new Preferences
                                  {
                                      OutputDirectory = ".",
                                      WriteUnchangedFiles = true
                                  };
            var templateLoader = new StringTemplateLoader();
            templateLoader.LoadTemplate(new [] {template});

            var container = new ConfigGenContainer();
            var singleFileGeneratorController = container.Resolve<ISingleFileGenerationController>();
            var results = singleFileGeneratorController.GenerateFile(templateLoader, configurationSettings, preferences);

            var generatedFile = results.GeneratedFile;
            if (!generatedFile.Exists)
            {
                return null;
            }

            resultingOutputDom.Load(generatedFile.FullName);
            return results;
        }

        public class StringTemplateLoader : ITemplateLoader
        {
            private TemplatePreferences _templatePreferences;
            private Stream _templateStream;

            public void LoadTemplate(string[] args)
            {
                var templateContents = args[0];
                var templatePreferencesLoader = new TemplatePreferencesLoader();

                using (var rawTemplateStream = new MemoryStream())
                using (var templateWriter = new StreamWriter(rawTemplateStream))
                {
                    _templateStream = new MemoryStream();
                    templateWriter.Write(templateContents);
                    templateWriter.Flush();
                    rawTemplateStream.Position = 0;
                    _templatePreferences = templatePreferencesLoader.LoadPreferences(rawTemplateStream, _templateStream);
                    _templateStream.Position = 0;
                }
            }

            public Stream GetTemplateStream()
            {
                return _templateStream;
            }

            public TemplatePreferences TemplatePreferences
            {
                get { return _templatePreferences;  }
            }
        }
    }
}
